package com.atguigu.gmall.seckill.mq;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.mq.MqConst;
import com.atguigu.gmall.seckill.component.SeckillLocalCache;
import com.atguigu.gmall.seckill.mq.to.SeckillSuccessTo;
import com.atguigu.gmall.seckill.service.SeckilService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.client.Channel;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author lsj
 * @create 2021-09-02 11:31
 */
@Component
public class SeckillMqListener {


    @Autowired
    SeckilService seckilService;
    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SeckillLocalCache localCache;

    @Autowired
    RedissonClient redissonClient;

    /**
     *     public static final String SECKILL_EXCHANGE = "exchange.seckill";
     *     public static final String SECKILL_UPGOOD_RK = "seckill.upgoods";
     *     public static final String SECKILL_UPGOOD_QUEUE = "seckill.upgoods.queue";
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = MqConst.SECKILL_UPGOOD_QUEUE,durable = "true",autoDelete = "false",exclusive = "false"),
                    exchange = @Exchange(value = MqConst.SECKILL_EXCHANGE,durable = "true",autoDelete = "false",type="topic"),
                    key = {MqConst.SECKILL_UPGOOD_RK}
            )
    })
    public void listenUpGoodsNews(Message message, Channel channel, String date) throws IOException {
        try{
            // 1、清空昨天的
            seckilService.clearBeforeDatas();
            //this.getCurrentSeckillGoods(); 不去切面，而是去方法体
            //seckilService.getCurrentSeckillGoods();走切面的逻辑
            //redis中会保存商品信息，锁
            // 2、查询当前系统需要上架的所有商品并保存到redis，这里使用了切面来放入缓存的
            List<SeckillGoods> goods = seckilService.getCurrentSeckillGoods();
            //让所有的seckill同步商品
            //发给redis一个消息，所有的商品上架了
            // 3、发送通知给其他人，同步本地缓存
            redisTemplate.convertAndSend(SeckillLocalCache.channel_key,SeckillLocalCache.goods_up);

            // 4、每个商品的库存作为分布式信号量【预热】，非幂等的,
            // 由于定时任务上架商品会给消息队列发很多消息，因为一个秒杀微服务中就有一个定时器就会发送一个消息，有很多秒杀微服务的话就会有很多定时器就会发送很多消息
            for (SeckillGoods good : goods) {
                //seckill:stock:51 = 8
                RSemaphore semaphore = redissonClient.getSemaphore(RedisConst.SECKILL_STOCK_PREFIX + good.getSkuId());
                int count = good.getNum() - good.getStockCount();
                //准备商品可以秒杀的数量
                //判断如果信号量已经做了就不用做了
                //返回信号量中有多少信号
                //   这样某一刻有人消费量信号量，那不还是会重复累加？不存在的  9:00
//                int result = semaphore.availablePermits();

                //就是redis的信号量是空的 和库存有不一样代表信号量和库存不一致  商品没有售完 如果执行队列就会减 。一样代表商品售完了就不减了是么
                //初始化信号  1:00 就已经做完了
                //五个会同时进入if里面吗？ 确实会有。
                semaphore.trySetPermits(count);
                //设置信号量自动回收机制。当天结束就完成
                semaphore.expire(1, TimeUnit.DAYS);


            }
            // 手动确认收到，参数：① 投递标签（即该消息的唯一标识）；② 是否接收多个消息（false则表示只接受一个）
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            // 手动返回接收消息失败，参数：① 投递标签（即该消息的唯一标识）；② 是否接收多个消息（false则表示只接受一个）；③ 是否回到原队列（true表示回，false表示不回则默认删除）
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }

    }


    /**
     * 秒杀信号量占位成功，就会生成订单信息保存到redis
     * @param message
     * @param channel
     * @param success
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = MqConst.SECKILL_SUCCESS_QUEUE,durable = "true",autoDelete = "false",exclusive = "false"),
                    exchange = @Exchange(value = MqConst.SECKILL_EXCHANGE,durable = "true",autoDelete = "false",type="topic"),
                    key = {MqConst.SECKILL_SUCCESS_RK}
            )
    })
    public void listenSeckillSuccess(Message message, Channel channel, SeckillSuccessTo success){
        //收到秒杀成功的数据。预准备订单信息
        //生成一个订单存到redis中。redis
        //根据购买的商品准备一个订单放在redis中, 秒杀码 userId+skuId （活动期间只能做一次）
        try{
            OrderInfo orderInfo = prepareOrder(success);
            //保存订单到redis
            String string = new ObjectMapper().writeValueAsString(orderInfo);

            //给redis中保留
            redisTemplate.opsForValue().set(RedisConst.SECKILL_USER+orderInfo.getUserId(),string);
            //操作集合

            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            try {
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

    }

    private OrderInfo prepareOrder(SeckillSuccessTo success){
        SeckillGoods goods = localCache.getSeckillGoodsBySkuId(success.getSkuId());
        OrderInfo orderInfo = new OrderInfo();


        List<OrderDetail> details = new ArrayList<>();
        OrderDetail detail = new OrderDetail();
        detail.setSkuId(goods.getSkuId());
        detail.setSkuName(goods.getSkuName());
        detail.setImgUrl(goods.getSkuDefaultImg());
        detail.setOrderPrice(goods.getCostPrice());
        detail.setSkuNum(1);

        details.add(detail);
        orderInfo.setOrderDetailList(details); //秒杀购买的商品
        orderInfo.setUserId(Long.parseLong(success.getUserId())); //秒杀的用户

        //订单计算一下总额
        orderInfo.sumTotalAmount();


        return orderInfo;
    }
}